/*
 * main.c -- the bare scull char module
 *
 * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
 * Copyright (C) 2001 O'Reilly & Associates
 *
 * The source code in this file can be freely used, adapted,
 * and redistributed in source or binary form, so long as an
 * acknowledgment appears in derived source files.  The citation
 * should list that the code comes from the book "Linux Device
 * Drivers" by Alessandro Rubini and Jonathan Corbet, published
 * by O'Reilly & Associates.   No warranty is attached;
 * we cannot take responsibility for errors or fitness for use.
 *
 */


#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>

#include <linux/kernel.h>	/* printk() */
#include <linux/slab.h>		/* kmalloc() */
#include <linux/fs.h>		/* everything... */
#include <linux/errno.h>	/* error codes */
#include <linux/types.h>	/* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h>	/* O_ACCMODE */
#include <linux/seq_file.h>
#include <linux/cdev.h>
#include <linux/sched.h>  /* current and everything */


#include <asm/system.h>		/* cli(), *_flags */
#include <asm/uaccess.h>	/* copy_*_user */
#include <asm/atomic.h>

#include "IO_port.h"		/* local definitions */

/*
 * Our parameters which can be set at load time.
 */
int IO_port_major =   0;
int IO_port_minor =   0;

unsigned int gpjcon_old =0;
unsigned int gpjdat_old =0;
unsigned int gpjup_old  =0;

module_param(IO_port_major, int, S_IRUGO);
module_param(IO_port_minor, int, S_IRUGO);



struct IO_port_dev *IO_port_devices;	/* allocated in scull_init_module */
static atomic_t IO_port_available = ATOMIC_INIT(1);
static spinlock_t IO_port_lock = SPIN_LOCK_UNLOCKED;
static DECLARE_WAIT_QUEUE_HEAD(IO_port_wait);
struct resource *IO_port_resource;
/*
 * Open and close
 */

int IO_port_open(struct inode *inode, struct file *filp)
{
	struct IO_port_dev *dev; /* device information */

	spin_lock(&IO_port_lock);

	while (! atomic_dec_and_test (&IO_port_available)) {
		atomic_inc(&IO_port_available);
		spin_unlock(&IO_port_lock);
		if (filp->f_flags & O_NONBLOCK) return -EAGAIN;
		if (wait_event_interruptible (IO_port_wait, atomic_read (&IO_port_available)))
			return -ERESTARTSYS; /* tell the fs layer to handle it */
		
		spin_lock(&IO_port_lock);
	}

	spin_unlock(&IO_port_lock);

	dev = container_of(inode->i_cdev, struct IO_port_dev, cdev);

	/* now trim to 0 the length of the device if open was write-only */
	gpjcon_old = (unsigned int) inl((unsigned long)S3C2440_GPJCON);
	gpjdat_old = (unsigned int) inl((unsigned long)S3C2440_GPJDAT);
	gpjup_old = (unsigned int) inl((unsigned long)S3C2440_GPJUP);
	
	mb();

	outl(0x01555555,(unsigned long)S3C2440_GPJCON);
//	printk( "IO_port con : %x\n", inl(S3C2440_GPJCON));
	outl(0x1fff,(unsigned long)S3C2440_GPJUP);
//	printk( "IO_port up : %x\n", inl(S3C2440_GPJUP));
	mb();

	outl(0x0,(unsigned long)S3C2440_GPJDAT);
	filp->private_data = dev; /* for other methods */

	return nonseekable_open(inode, filp);          /* success */
}

int IO_port_release(struct inode *inode, struct file *filp)
{
	outl(gpjcon_old,(unsigned long)S3C2440_GPJCON);
	outl(gpjup_old,(unsigned long)S3C2440_GPJUP);
	outl(gpjdat_old,(unsigned long)S3C2440_GPJDAT);
	atomic_inc(&IO_port_available); /* release the device */
	wake_up_interruptible_sync(&IO_port_wait); /* awake other uid's */

	return 0;
}

/*
 * The ioctl() implementation
 */

int IO_port_ioctl(struct inode *inode, struct file *filp,
                 unsigned int cmd, unsigned long arg)
{

	int err = 0;
	int retval = 0;
	unsigned int current_status;
  	/*
	 * extract the type and number bitfields, and don't decode
	 * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
	 */
	if (_IOC_TYPE(cmd) != IO_PORT_MAGIC) return -ENOTTY;
	if (_IOC_NR(cmd) > IO_PORT_MAXNR) return -ENOTTY;

	/*
	 * the direction is a bitmask, and VERIFY_WRITE catches R/W
	 * transfers. `Type' is user-oriented, while
	 * access_ok is kernel-oriented, so the concept of "read" and
	 * "write" is reversed
	 */
	if (_IOC_DIR(cmd) & _IOC_READ)
		err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
	if (err) return -EFAULT;

	switch(cmd) {

	  case IO_PORT_0:
	outl(0x0,(unsigned long)S3C2440_GPJDAT);
	break;
        
	  case IO_PORT_1: /* Set: arg points to the value */
	outl(0x1,(unsigned long)S3C2440_GPJDAT);
	break;

	  case IO_PORT_2: /* Tell: arg is the value */
	outl(0x2,(unsigned long)S3C2440_GPJDAT);
	break;

	  case IO_PORT_3: /* Get: arg is pointer to result */
	outl(0x3,(unsigned long)S3C2440_GPJDAT);
	break;

	case IO_PORT_STATUS: /* eXchange: use arg as pointer */
	current_status = (unsigned int) inl((unsigned long)S3C2440_GPJDAT);
	retval = __put_user(current_status, (unsigned int __user *)arg);
	break;

	  default:  /* redundant, as cmd was checked against MAXNR */
		return -ENOTTY;
	}
	return retval;

}

struct file_operations IO_port_fops = {
	.owner =    THIS_MODULE,
	.ioctl =    IO_port_ioctl,
	.open =     IO_port_open,
	.release =  IO_port_release,
	.llseek =		no_llseek,

};

/*
 * Finally, the module stuff
 */

/*
 * The cleanup function is used to handle initialization failures as well.
 * Thefore, it must be careful to work correctly even if some of the items
 * have not been initialized
 */
void IO_port_cleanup_module(void)
{
	dev_t devno = MKDEV(IO_port_major, IO_port_minor);

	if (IO_port_resource!=NULL) release_region((unsigned long)S3C2440_GPJCON, 0x0c);

	/* Get rid of our char dev entries */
	if (IO_port_devices) {
		cdev_del(&IO_port_devices->cdev);
		kfree(IO_port_devices);
	}

	

	/* cleanup_module is never called if registering failed */
	unregister_chrdev_region(devno, 1);


}


/*
 * Set up the char_dev structure for this device.
 */
static void IO_port_setup_cdev(struct IO_port_dev *dev)
{
	int err, devno = MKDEV(IO_port_major, IO_port_minor);
    
	cdev_init(&dev->cdev, &IO_port_fops);
	dev->cdev.owner = THIS_MODULE;
	err = cdev_add (&dev->cdev, devno, 1);
	/* Fail gracefully if need be */
	if (err)
		printk(KERN_NOTICE "Error %d adding IO_port", err);
}


int IO_port_init_module(void)
{
	int result;
	dev_t dev = 0;

/*
 * Get a range of minor numbers to work with, asking for a dynamic
 * major unless directed otherwise at load time.
 */
	if (IO_port_major) {
		dev = MKDEV(IO_port_major, IO_port_minor);
		result = register_chrdev_region(dev, 1, "IO_port");
	} else {
		result = alloc_chrdev_region(&dev, IO_port_minor, 1,
				"IO_port");
		IO_port_major = MAJOR(dev);
	}
	if (result < 0) {
		printk(KERN_WARNING "IO_port: can't get major %d\n", IO_port_major);
		return result;
	}

        /* 
	 * allocate the devices -- we can't have them static, as the number
	 * can be specified at load time
	 */
	IO_port_devices = kmalloc(sizeof(struct IO_port_dev), GFP_KERNEL);
	if (!IO_port_devices) {
		result = -ENOMEM;
		goto fail;  /* Make this more graceful */
	}
	memset(IO_port_devices, 0, sizeof(struct IO_port_dev));

        /* Initialize each device. */
		init_MUTEX(&IO_port_devices->sem);
		 IO_port_setup_cdev(IO_port_devices);
	if ((IO_port_resource=request_region((unsigned long)S3C2440_GPJCON, 0x0c,"IO_port"))==NULL) 
		goto fail;


	return 0; /* succeed */

  fail:
	IO_port_cleanup_module();
	return result;
}

module_init(IO_port_init_module);
module_exit(IO_port_cleanup_module);

MODULE_AUTHOR("Tekkaman Ninja");
MODULE_LICENSE("Dual BSD/GPL");
